﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace LocalSend2 {
	internal partial class MainForm : Form {

		/**
		 * 0、终端识别码为：IP+PORT+NAME(可空)，以下简称 ID
		 * 1、上线监听广播，接收到的 ID 即时刷新到列表中
		 * 2、上线后定时（间隔3s）发送广播，报告自己的 ID，并且删除超过10s未接收到报告的终端
		 * 3、每个终端有一个“共享文件列表”
		 * 4、终端需要接收另一终端共享的文件时，需要刷新（点击）该终端的共享文件列表
		 * 5、可以将上述文件列表中的文件，拖放到当前终端，本地路径中
		 */

		const string TITLE_BASE = "局域网文件传输终端";
		const string VERSION_NO = "0.7";

		/// <summary>
		/// 本地共享文件列表
		/// </summary>
		static List<ShareFile> LocalFileList = new List<ShareFile>();

		/// <summary>
		/// 在线的终端列表
		/// </summary>
		static List<Client> OnlineClientList = new List<Client>();

		/// <summary>
		/// 当前终端使用的发送、接收端口
		/// </summary>
		UdpClient udp = new UdpClient(53318);

		/// <summary>
		/// 当前终端信息
		/// </summary>
		Client self = null;

		/// <summary>
		/// 文件传输缓冲区大小
		/// </summary>
		const int FILE_BUFFER_SIZE = 512 * 1024;

		public MainForm() {
			InitializeComponent();
			Text = TITLE_BASE + "_" + VERSION_NO;

			// 初始化本机信息
			string ipStr = "";
			foreach (IPAddress ia in Dns.GetHostAddresses(Dns.GetHostName())) {
				if (ia.AddressFamily == AddressFamily.InterNetwork) {
					ipStr = ia.ToString();
					break;
				}
			}
			if (ipStr == "") {
				MessageBox.Show("未获取到本机IP，请检查网络并确保使用的是IPV4");
				Close();
				return;
			}

			string nickName = "";
			string savePath = "";
			// 读取配置文件
			string cfgFilePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\LocalSend2";
			string cfgFileName = cfgFilePath + "\\0.cfg";
			if (!File.Exists(cfgFileName)) {
				// 当前客户端名称
				nickName = Dns.GetHostName().Replace("|", "");
				if (nickName.StartsWith("DESKTOP-"))
					nickName = nickName.Substring(8);

				// 下载文件夹
				string doctPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
				string downloadPath = doctPath.Substring(0, doctPath.LastIndexOf('\\')) + "\\Downloads";
				if (Directory.Exists(downloadPath))
					savePath = downloadPath;
				else
					savePath = doctPath;
			} else {
				foreach (string line in File.ReadAllLines(cfgFileName)) {
					if (line.StartsWith("MyNickName="))
						nickName = line.Substring("MyNickName=".Length);
					else if (line.StartsWith("SavePath="))
						savePath = line.Substring("SavePath=".Length);
					else if (line.StartsWith("OpenPathAfterDownload="))
						chkOpenPath.Checked = bool.Parse(line.Substring("OpenPathAfterDownload=".Length));
					else if (line.StartsWith("EndPointClient="))
						txtEndPoint.Text = line.Substring("EndPointClient=".Length);
				}
			}

			// 本机信息
			self = new Client {
				Ip = ipStr,
				Port = 0,
				NickName = nickName,
				LastAliveTime = DateTime.Now
			};
			txtName.Text = nickName;
			txtSavePath.Text = savePath;
		}

		// 启动之后
		private void MainForm_Load(object sender, EventArgs e) {

			this.Location = new Point(180, 18);

			// 监听广播线程，接收其它终端的身份信息
			Thread th = new Thread(new ThreadStart(ListenBroadcastThread));
			th.IsBackground = true;
			th.Start();

			// 发送本机 ID 线程，同时清理长时间未更新的其它终端信息
			th = new Thread(new ThreadStart(BroadcastIdThread));
			th.IsBackground = true;
			th.Start();

			// 监听并处理其它终端请求
			th = new Thread(new ThreadStart(WaitClientThread));
			th.IsBackground = true;
			th.Start();

			// 刷新本机共享文件信息
			th = new Thread(new ThreadStart(RefreshLocalFileListThread));
			th.IsBackground = true;
			th.Start();
		}

		// 关闭窗口后
		private void MainForm_FormClosed(object sender, FormClosedEventArgs e) {
			// 保存配置文件
			string cfgFilePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\LocalSend2";
			string cfgFileName = cfgFilePath + "\\0.cfg";

			if (!Directory.Exists(cfgFilePath)) {
				Directory.CreateDirectory(cfgFilePath);
			}

			string[] cfgContent = new string[] {
				"MyNickName=" + txtName.Text.Trim(),
				"SavePath=" + txtSavePath.Text.Trim(),
				"OpenPathAfterDownload=" + chkOpenPath.Checked,
				"EndPointClient=" + txtEndPoint.Text.Trim(),
			};

			File.WriteAllLines(cfgFileName, cfgContent);
		}

		// 刷新本机共享文件信息
		void RefreshLocalFileListThread() {

			while (!IsDisposed) {
				Thread.Sleep(1000);
				if (LocalFileList == null) continue;

				foreach (ShareFile file in LocalFileList) {
					// 删除的文件，这里仅标记不自动删除
					if (!File.Exists(file.FullName)) {
						file.Length = 0;
						continue;
					}
					FileInfo fileInfo = new FileInfo(file.FullName);
					file.UpdateInfo(fileInfo);
				}
				RefreshShareFileList();
			}
		}

		// 监听连接线程
		void WaitClientThread() {
			// 找一个端口来监听
			TcpListener tcp = null;
			int portMin = 53318, tryTimes = 1000;
			for (int i = 0; i < tryTimes; ++i) {
				try {
					tcp = new TcpListener(IPAddress.Any, portMin + i);
					self.Port = portMin + i;
					break;
				} catch (Exception) {
					tcp = null;
					continue;
				}
			}
			if (tcp == null) {
				throw new Exception("端口被占用：" + portMin + "~" + (portMin + tryTimes));
			}
			BeginInvoke(new Action(() => {
				lblMyIpPort.Text = self.Ip + ":" + self.Port;
			}));

			tcp.Start(4);
			// 处理其它客户端的连接
			while (!IsDisposed) {
				TcpClient tcpClient = tcp.AcceptTcpClient();
				Thread thread = new Thread(new ParameterizedThreadStart(SessionThread));
				thread.Start(tcpClient);
			}
		}

		// 处理一个连接
		void SessionThread(dynamic tcpClientO) {
			TcpClient tcpClient = tcpClientO;
			NetworkStream netStream = tcpClient.GetStream();
			byte[] bytes = new byte[10240];
			int len = netStream.Read(bytes, 0, 10240);
			string recvStr = Encoding.Default.GetString(bytes, 0, len);

			// 获取文件列表，文件列表少于15个
			if (recvStr == "GET_FILE_LIST") {
				List<string> fileStrList = new List<string>();
				LocalFileList.ForEach((f) => { fileStrList.Add(f.FileInfoStr); });
				string sendStr = string.Join("`", fileStrList);
				if (sendStr.Length == 0) sendStr = "NO_FILE";
				bytes = Encoding.Default.GetBytes(sendStr);
				netStream.Write(bytes, 0, bytes.Length);
				netStream.Close();
				return;
			}

			// 下载文件，DOWNLOAD_FILE|ID
			if (recvStr.StartsWith("DOWNLOAD_FILE|")) {
				// 发送文件内容
				int fileId = int.Parse(recvStr.Split('|')[1]);
				ShareFile file = LocalFileList.Where(f => f.ID == fileId).FirstOrDefault();
				//if (file != null && file.Length != 0) {
				if (file != null && File.Exists(file.FullName)) {
					bytes = new byte[FILE_BUFFER_SIZE];
					using (FileStream fs = File.Open(file.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
						while (true) {
							len = fs.Read(bytes, 0, FILE_BUFFER_SIZE);
							//if (len == 0) break;
							try { netStream.Write(bytes, 0, len); }
							catch (Exception) { break; }
							if (fs.Position == fs.Length) break;
						}
					}
					netStream.Close();
					tcpClient.Close();
					//fs.Close();
				} else {
					// 文件不存在直接断开
					netStream.Close();
					tcpClient.Close();
				}
			}
		}

		// 监听广播线程，其它客户端信息
		void ListenBroadcastThread() {
			IPEndPoint ipep = new IPEndPoint(0, 0); // 发送端终端信息（暂时不用）
			while (!IsDisposed) {
				byte[] bytes = udp.Receive(ref ipep);
				string recvStr = Encoding.Default.GetString(bytes);
				UpdateClientInfo(recvStr);
			}
		}

		// 发送ID线程
		void BroadcastIdThread() {

			while (!IsDisposed) {
				// 发送自己的ID
				Thread.Sleep(2000);
				self.NickName = txtName.Text; // 刷新昵称
				self.NickName = self.NickName.Trim().Replace("|", "");
				if (self == null) continue;
				byte[] bytes = Encoding.Default.GetBytes(self.Ip + "|" + self.Port + "|" + self.NickName);
				udp.Send(bytes, bytes.Length, new IPEndPoint(IPAddress.Broadcast, 53318));

				// 清理长时间未收到验证的Client
				OnlineClientList.RemoveAll(o => (DateTime.Now - o.LastAliveTime).TotalSeconds > 5);
				// 更新显示列表
				BeginInvoke(new Action(() => {
					int selIndex = listClient.SelectedIndex;
					listClient.Items.Clear();
					if (OnlineClientList == null || OnlineClientList.Count == 0) return;
					listClient.Items.AddRange(OnlineClientList.ToArray());
					listClient.DisplayMember = "DisplayName";
					if (selIndex > -1 && listClient.Items.Count > selIndex)
						listClient.SelectedIndex = selIndex;
				}));
			}
		}

		/// <summary>
		/// 更新某客户端信息
		/// </summary>
		/// <param name="idStr">接收到客户端的“ID”（IP|PORT|NAME）</param>
		void UpdateClientInfo(string idStr) {
			if (string.IsNullOrEmpty(idStr)) return;
			string[] sl = idStr.Split('|');
			if (sl.Length != 3) return;

			// 自己的广播信息
			if (self != null && sl[0] == self.Ip) {
				//self.NickName = sl[2];
				//self.LastAliveTime = DateTime.Now;
				return;
			}

			BeginInvoke(new Action(() => {
				if (!IsDisposed) lblBroadcastId.Text = idStr + " - " + DateTime.Now;
			}));

			// 其它客户端信息，
			Client client = OnlineClientList.Where(c =>
				c.Ip == sl[0] && c.Port.ToString() == sl[1]).FirstOrDefault();
			if (client != null) {
				// 更新
				client.NickName = sl[2];
				client.LastAliveTime = DateTime.Now;
			} else {
				// 新增
				OnlineClientList.Add(new Client {
					Ip = sl[0],
					Port = int.Parse(sl[1]),
					NickName = sl[2],
					LastAliveTime = DateTime.Now
				});
			}
		}

		// 刷新本地共享文件显示列表
		void RefreshShareFileList() {
			BeginInvoke(new Action(() => {
				int selIndex = listShareFile.SelectedIndex;
				listShareFile.Items.Clear();
				if (LocalFileList == null) return;
				listShareFile.Items.AddRange(LocalFileList.ToArray());
				listShareFile.DisplayMember = "DisplayName";
				if (selIndex > -1 && selIndex < listShareFile.Items.Count)
					listShareFile.SelectedIndex = selIndex;
			}));
		}

		DownloadMask dMask = null;
		ShareFile fileSel = null;
		void DownloadFileThreadFunc(object tcpObj) {
			if (fileSel == null) return;
			TcpClient tcp = tcpObj as TcpClient;

			// 发送文件下载请求
			NetworkStream netStream = tcp.GetStream();
			byte[] bytes = Encoding.Default.GetBytes("DOWNLOAD_FILE|" + fileSel.ID);
			netStream.Write(bytes, 0, bytes.Length);

			// 下载文件
			string saveFilePath = txtSavePath.Text + "\\" + fileSel.Name;
			FileStream fs = new FileStream(saveFilePath, FileMode.Create);
			bytes = new byte[FILE_BUFFER_SIZE];
			long downLength = 0;

			int dPercentL = 0;
			long downLengthMark = 0;
			DateTime downTimeMark = DateTime.Now;

			while (downLength < fileSel.Length) {
				// 读网络数据
				int len = 0;
				try {
					len = netStream.Read(bytes, 0, FILE_BUFFER_SIZE);
					if (len == 0) throw new IOException("上传端主动中断连接。"); // 终端主动中断连接（请求的文件已取消共享）
				} catch (Exception e) {
					int dPercent = (int)(downLength * 100 / fileSel.Length);
					MessageBox.Show("数据接收中断，接收进度 " + dPercent + "%\n" + e.Message,
						"下载失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
					break;
				}
				// 写入文件
				fs.Write(bytes, 0, len);
				downLength += len;

				if (dMask != null) {
					// 下载进度
					int dPercent = (int)(downLength * 100 / fileSel.Length);
					if (dPercent != dPercentL) {
						dMask.SetProgress(dPercent);
						dPercentL = dPercent;
					}
					// 下载速度
					DateTime timeNow = DateTime.Now;
					TimeSpan timeSpan = timeNow - downTimeMark;
					if (timeSpan.TotalMilliseconds > 500) {
						string speedStr = ((downLength - downLengthMark) / 1024.0 / 1024.0 /
							timeSpan.TotalMilliseconds * 1000).ToString("f2") + "MB/s";
						dMask.SetDownloadSpeed(speedStr);
						downLengthMark = downLength;
						downTimeMark = timeNow;
					}
				}
			}

			fs.Close();
			netStream.Close();
			tcp.Close();

			if (dMask != null)
				BeginInvoke(new Action(() => { Controls.Remove(dMask); }));

			if (chkOpenPath.Checked)
				Process.Start(txtSavePath.Text);
			else
				MessageBox.Show("下载完成，" + saveFilePath);
		}

		// 下载选中的文件
		private void btnDownload_Click(object sender, EventArgs e) {
			fileSel = listRemoteFileList.SelectedItem as ShareFile;
			Client clientSel = listClient.SelectedItem as Client;
			if (clientSel == null && epClient != null) {
				clientSel = epClient;
			}
			if (fileSel == null || clientSel == null) return;

			DialogResult dr = MessageBox.Show("点击确定开始下载\n" + fileSel.Name, "",
				MessageBoxButtons.OKCancel, MessageBoxIcon.Information);
			if (dr != DialogResult.OK) return;

			TcpClient tcp = new TcpClient(clientSel.Ip, clientSel.Port);
			if (!tcp.Connected) {
				MessageBox.Show("终端连接失败");
				return;
			}

			// 下载蒙板
			dMask = null;
			if (fileSel.Length > 1024 * 1024) {
				string fileSizeStr = "";
				if (fileSel.Length < 1024 * 1024 * 1024)
					fileSizeStr = (fileSel.Length / 1024.0 / 1024.0).ToString("f2") + "MB";
				else
					fileSizeStr = (fileSel.Length / 1024.0 / 1024.0 / 1024.0).ToString("f2") + "GB";
				dMask = new DownloadMask(fileSel.Name, txtSavePath.Text, fileSizeStr);
				dMask.Dock = DockStyle.Fill;
				this.Controls.Add(dMask);
				dMask.BringToFront();
			}

			Thread thDownload = new Thread(new ParameterizedThreadStart(DownloadFileThreadFunc));
			thDownload.IsBackground = true;
			thDownload.Start(tcp);
		}

		// 选择下载保存路径
		private void txtSavePath_DoubleClick(object sender, EventArgs e) {
			FolderBrowserDialog fbd = new FolderBrowserDialog();
			if (fbd.ShowDialog() != DialogResult.OK
				|| !Directory.Exists(fbd.SelectedPath)) return;

			txtSavePath.Text = fbd.SelectedPath;
		}

		// 点击终端列表
		private void listClient_MouseUp(object sender, MouseEventArgs e) {
			Client selClient = listClient.SelectedItem as Client;
			if (selClient == null) return;
			txtEndPoint_TextChanged(sender, e);

			List<ShareFile> fileList = null;
			try { fileList = selClient.GetFileList(); } catch { }
			if (fileList == null) {
				MessageBox.Show("列表刷新失败，请检查网络或稍候再试。");
				return;
			}

			BeginInvoke(new Action(() => {
				listRemoteFileList.Items.Clear();
				listRemoteFileList.Items.AddRange(fileList.ToArray());
				listRemoteFileList.DisplayMember = "DisplayName";
			}));
		}

		// 共享列表文件拖入
		private void listShareFile_DragEnter(object sender, DragEventArgs e) {
			if (e.Data.GetDataPresent(DataFormats.FileDrop))
				e.Effect = DragDropEffects.Link;
			else
				e.Effect = DragDropEffects.None;
		}

		// 共享列表文件放置
		private void listShareFile_DragDrop(object sender, DragEventArgs e) {
			string[] pathList = (string[])e.Data.GetData(DataFormats.FileDrop);
			if (pathList.Length == 0) return;

			// 共享文件限制（文件信息发送长度）
			if (listShareFile.Items.Count + pathList.Length > 15) {
				MessageBox.Show("选择的文件太多了，删除一些再试");
				return;
			}

			foreach (string s in pathList) {
				if (LocalFileList.Any(f => f.FullName == s)) continue; // 重复文件
				FileInfo fileInfo = new FileInfo(s);
				LocalFileList.Add(new ShareFile {
					Name = fileInfo.Name,
					FullName = fileInfo.FullName,
					Length = fileInfo.Length,
					LastWriteTime = fileInfo.LastWriteTime,
				});
			}

			RefreshShareFileList();
		}

		// 本地共享文件删除操作
		private void listShareFile_KeyUp(object sender, KeyEventArgs e) {
			if (e.KeyCode != Keys.Delete) return;

			ShareFile fileSel = listShareFile.SelectedItem as ShareFile;
			if (fileSel == null) return;

			LocalFileList.Remove(fileSel);
			RefreshShareFileList();
		}

		// 清空本地文件列表
		private void btnClear_Click(object sender, EventArgs e) {
			LocalFileList = new List<ShareFile>();
			RefreshShareFileList();
		}

		// 窗口置顶
		private void chkTopmost_CheckedChanged(object sender, EventArgs e) {
			this.TopMost = chkTopmost.Checked;
			this.Opacity = this.TopMost ? 0.9 : 1;
		}

		// 指定的终端
		Client epClient = null;
		// 获取指定终端文件列表
		private void txtEndPoint_KeyUp(object sender, KeyEventArgs e) {
			if (e.KeyCode != Keys.Enter) return;

			// 初始化显示
			listClient.SelectedIndex = -1;
			lblEndPointConnected.Text = "未连接";
			lblEndPointConnected.ForeColor = Color.Gray;

			// IP & PORT 解析
			string[] ipPort = txtEndPoint.Text.Split(':');
			if (ipPort.Length != 2) return;
			string ipStr = ipPort[0];
			int port = int.Parse(ipPort[1]);

			// 获取文件列表
			epClient = new Client {
				Ip = ipStr,
				Port = port,
			};
			List<ShareFile> shareList = null;
			try { shareList = epClient.GetFileList(); } catch { }
			if (shareList == null) {
				epClient = null;
				MessageBox.Show("终端连接失败");
				return;
			}

			// 显示文件列表
			BeginInvoke(new Action(() => {
				listRemoteFileList.Items.Clear();
				listRemoteFileList.Items.AddRange(shareList.ToArray());
				listRemoteFileList.DisplayMember = "DisplayName";
			}));

			// 回显终端信息
			lblEndPointConnected.Text = "已连接";
			lblEndPointConnected.ForeColor = Color.Black;
			//txtEndPoint.BackColor = Color.LightBlue;
		}

		// 指定终端信息改变
		private void txtEndPoint_TextChanged(object sender, EventArgs e) {
			epClient = null;
			lblEndPointConnected.Text = "未连接";
			lblEndPointConnected.ForeColor = Color.Gray;
		}
	}
}
